const net = require('net');

const SocketState = Object.freeze({
    UNINITIALIZED: 'UNINITIALIZED',
    CLOSED: 'CLOSED',
    CONNECTED: 'CONNECTED'
})

class SocketClient{
	constructor(name, ip, port, timeout=60000){
		this.name = name;
		this.ip = ip;
		this.port = port;
		this.timeout = timeout;
		this.state = SocketState.UNINITIALIZED;
		this.socket = null;
		this.connectCb = null;
		this.errorCb = null; 
		this.recvCb = null; 
		this.closeCb = null;
	}
	_error(err){
		if (global.log) {
			global.log.error(`${this.name} ${err}`);
		} else {
			console.error(`${this.name} ${err}`);
		}
	}
	init(connectCallback, errorCallback, recvCallback, closeCallback){
		this.connectCb = connectCallback;
		this.errorCb = errorCallback; 
		this.recvCb = recvCallback; 
		this.closeCb = closeCallback;
		this.createSocket();
		if (this.connectCb) {
			this.socket.connect(this.port ,this.ip, ()=>{
				if (global.log) {
					global.log.print(`${this.name} connected1 to:${this.ip}:${this.port} success`);
				} else {
					console.log(`${this.name} connected1 to:${this.ip}:${this.port} success`);
				}
				this.state = SocketState.CONNECTED;
				this.connectCb(this.socket);
			});
		}
	}
	createSocket(){
		this.socket = net.Socket();
		this.state = SocketState.CLOSED;
		let self = this;
		this.socket.setEncoding('binary');
		this.socket.setTimeout(this.timeout);
		this.socket.on('timeout', function() {
			this.end();
		});
		this.socket.on("close", function() {
			if (global.log) {
                global.log.print(`${self.name} close`);
            } else {
                console.log(`${self.name} close`);
            }
			if (self.closeCb) {
				self.closeCb(this);
			}
			self.state = SocketState.CLOSED;
		});
		this.socket.on("error", function(err) {
			self._error(err);
			if (self.errorCb) {
				self.errorCb(this, err);
			}
		});
		this.socket.on("data", function(data) {
			// console.log(data);
			if (self.recvCb) {
				let buff = null;
				if (typeof data === 'string') {
					buff = Buffer.alloc(data.length);
					for (let i = 0; i < data.length; i++) {
						buff[i] = data.charCodeAt(i);
					}
				} else {
					buff = Buffer.from(data);
				}
				self.recvCb(this, buff);
			}
		});
	}
	send(data){
		if (this.state==SocketState.CONNECTED) {
			try {
				this.socket.write(data);
				return true;
			} catch (err) {
				this._error(err);
				return false;
			}
		}
		return false;
	}
	close(){
		if (this.socket) {
			this.socket.end();
			this.socket = null;
		}
	}
	trySend(data){
		if (this.state==SocketState.CONNECTED) {
			try {
				this.socket.write(data);
			} catch (err) {
				this._error(err);
			}
		} else {
			this.close();
			this.createSocket();
			this.socket.connect(this.port, this.ip, ()=>{
				this.state = SocketState.CONNECTED;
				if (global.log) {
					global.log.print(`${this.name} connected2 to:${this.ip}:${this.port} success`);
				} else {
					console.log(`${this.name} connected2 to:${this.ip}:${this.port} success`);
				}
				try {
					this.socket.write(data);
				} catch (err) {
					this._error(err);
				}
			});
		}
	}
	static RequestOnce(ip, port, data) {
		return new Promise(function (resolve, reject) {
			let socket = net.Socket();
			socket.setEncoding('binary');
			socket.setTimeout(10000);
			socket.on('timeout', ()=>{
				socket.end();
			});
			socket.on("close", ()=>{
				resolve(["close", null]);
			});
			socket.on("error", (err)=>{
				resolve(["error", err]);
			});
			socket.on("data", function(res) {
				let buff = null;
				if (typeof res === 'string') {
					buff = Buffer.alloc(res.length);
					for (let i = 0; i < res.length; i++) {
						buff[i] = res.charCodeAt(i);
					}
				} else {
					buff = Buffer.from(res);
				}
				socket.end();
				resolve(["data", buff]);
			});
			socket.connect(port,ip,()=>{
				try {
					socket.write(data);
				} catch (err) {
                    reject(err);
				}
			});
        })
	}
}

module.exports = SocketClient;